package io.kiki.sba.registry.server.clientInterface.providedata;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.kiki.sba.registry.common.model.ClientToClientInterfaceServerNodeId;
import io.kiki.sba.registry.common.model.constants.ValueConstants;
import io.kiki.sba.registry.common.model.metaserver.ProvideData;
import io.kiki.sba.registry.server.clientInterface.bootstrap.Config;
import io.kiki.sba.registry.server.clientInterface.connections.ConnectionsService;
import io.kiki.sba.registry.server.clientInterface.filter.blacklist.BlacklistConfig;
import io.kiki.sba.registry.server.clientInterface.filter.blacklist.BlacklistConstants;
import io.kiki.sba.registry.server.clientInterface.filter.blacklist.MatchType;
import io.kiki.sba.registry.server.clientInterface.providedata.FetchBlackListService.BlacklistStorage;
import io.kiki.sba.registry.server.clientInterface.registry.Registry;
import io.kiki.sba.registry.server.shared.providedata.AbstractFetchSystemPropertyService;
import io.kiki.sba.registry.server.shared.providedata.SystemDataStorage;
import io.kiki.sba.registry.util.JsonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.*;
import java.util.Map.Entry;


public class FetchBlackListService extends AbstractFetchSystemPropertyService<BlacklistStorage> {

    private static final Logger logger = LoggerFactory.getLogger(FetchBlackListService.class);

    @Autowired
    private Registry sessionRegistry;

    @Autowired
    private ConnectionsService connectionsService;

    @Autowired
    private Config config;

    public FetchBlackListService() {
        super(ValueConstants.BLACK_LIST_DATA_ID, new BlacklistStorage(INIT_VERSION, new ArrayList<>()));
    }

    private Map<String, Map<String, Set<String>>> convertBlacklistConfig(String config, List<BlacklistConfig> blacklistConfigs) {

        TypeReference<Map<String, Map<String, Set<String>>>> typeReference = new TypeReference<Map<String, Map<String, Set<String>>>>() {
        };

        ObjectMapper mapper = JsonUtils.getJacksonObjectMapper();

        Map<String, Map<String, Set<String>>> blacklistConfigMap;
        try {
            blacklistConfigMap = mapper.readValue(config, typeReference);
        } catch (Throwable e) {
            logger.error("Parser config json error!", e);
            return null;
        }
        if (null == blacklistConfigMap) {
            logger.info("[cmd] setBlacklistConfig fail, params is null");
            return null;
        }
        try {
            for (Entry<String, Map<String, Set<String>>> configEntry : blacklistConfigMap.entrySet()) {
                BlacklistConfig blacklistConfig = new BlacklistConfig();
                blacklistConfig.setType(configEntry.getKey());

                List<MatchType> matchTypeList = new ArrayList();

                Map<String, Set<String>> matchTypeMap = configEntry.getValue();
                for (Entry<String, Set<String>> typeEntry : matchTypeMap.entrySet()) {
                    String type = typeEntry.getKey();

                    MatchType<String> ipFullMatchType = new MatchType();
                    ipFullMatchType.setType(type);
                    ipFullMatchType.setPatternSet(typeEntry.getValue());
                    matchTypeList.add(ipFullMatchType);
                }
                blacklistConfig.setMatchTypes(matchTypeList);
                blacklistConfigs.add(blacklistConfig);
            }
            return blacklistConfigMap;
        } catch (Throwable e) {
            logger.error("[cmd] setBlacklistConfig error", e);
            return null;
        }
    }

    private void clientOffBlackIp(Map<String, Map<String, Set<String>>> blacklistConfigMap) {
        if (blacklistConfigMap != null) {
            Set<String> ipSet = new HashSet();

            for (Map.Entry<String, Map<String, Set<String>>> configEntry : blacklistConfigMap.entrySet()) {
                if (BlacklistConstants.FORBIDDEN_PUB.equals(configEntry.getKey()) || BlacklistConstants.FORBIDDEN_SUB_BY_PREFIX.equals(configEntry.getKey())) {
                    Map<String, Set<String>> typeMap = configEntry.getValue();
                    if (typeMap != null) {
                        for (Map.Entry<String, Set<String>> typeEntry : typeMap.entrySet()) {
                            if (BlacklistConstants.IP_FULL.equals(typeEntry.getKey())) {
                                if (typeEntry.getValue() != null) {
                                    ipSet.addAll(typeEntry.getValue());
                                }
                            }
                        }
                    }
                }
            }

            List<ClientToClientInterfaceServerNodeId> conIds = connectionsService.getIpConnects(ipSet);
            // blacklist remove pub, sub, watch
            sessionRegistry.blacklist(conIds);
        }
    }

    @Override
    protected int getSystemPropertyIntervalMillis() {
        return config.getSystemPropertyIntervalMillis();
    }

    @Override
    protected boolean doProcess(BlacklistStorage expect, ProvideData provideData) {
        // black list data
        final String data = ProvideData.toString(provideData);
        if (data == null) {
            logger.warn("Fetch session blacklist content null");
            return false;
        }

        logger.info("Fetch session blacklist {}", data);

        List<BlacklistConfig> blacklistConfigs = new ArrayList();
        // {"FORBIDDEN_PUB":{"IP_FULL":["1.1.1.1"]},"FORBIDDEN_SUB_BY_PREFIX":{"IP_FULL":["1.1.1.1"]}}
        Map<String, Map<String, Set<String>>> blacklistConfigMap = convertBlacklistConfig(data, blacklistConfigs);
        clientOffBlackIp(blacklistConfigMap);

        // after cancel success
        try {
            BlacklistStorage update = new BlacklistStorage(provideData.getVersion(), blacklistConfigs);
            if (compareAndSet(expect, update)) {
                return true;
            }
        } catch (Throwable t) {
            logger.error("update blacklist:{} error.", provideData, t);
        }

        return false;
    }


    public List<BlacklistConfig> getBlacklistConfigList() {
        return storage.get().blacklistConfigList;
    }


    public FetchBlackListService setSessionRegistry(Registry sessionRegistry) {
        this.sessionRegistry = sessionRegistry;
        return this;
    }


    public FetchBlackListService setConnectionsService(ConnectionsService connectionsService) {
        this.connectionsService = connectionsService;
        return this;
    }


    public FetchBlackListService setSessionServerConfig(Config config) {
        this.config = config;
        return this;
    }

    protected static class BlacklistStorage extends SystemDataStorage {
        final List<BlacklistConfig> blacklistConfigList;

        public BlacklistStorage(long version, List<BlacklistConfig> blacklistConfigList) {
            super(version);
            this.blacklistConfigList = blacklistConfigList;
        }
    }
}
